/*
 * Copyright 2004 original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jbfilter.jsp.fcomps;

import javax.servlet.jsp.JspException;

import org.apache.commons.lang.StringUtils;
import org.jbfilter.core.fcomps.single.SingleFilterComponent;
import org.jbfilter.jsp.FilterTagCallable;
import org.jbfilter.jsp.JbfilterTagAttribute;
import org.jbfilter.jsp.converters.Converter;
import org.jbfilter.jsp.converters.StandardConverter;


/**
 * A {@link SingleFilterComponentTag} where the {@code value} must be converted.
 * @author Marcus Adrian
 * @param <E> the type of the beans to filter
 * @param <T> the property's type
 */
public abstract class ConverterSingleFilterComponentTag<E, T>
	extends
		SingleFilterComponentTag<E, T, Object>
	implements
		FilterTagCallable {

	private Object converter;// accepts strings or Converter's
	private String varValueString;// if set : puts the string converted value into the page

	// the retrieved converter
	private Converter<T> conv; // use getter only !
	
	public void setConverter(Object converter) {
		this.converter = converter;
	}
	
	@Override
	protected void setPropertiesSingleFilterComponentTagCallback() throws JspException {
		setPropertiesConverterSingleFilterComponentTagCallback();
	}
	
	abstract protected void setPropertiesConverterSingleFilterComponentTagCallback() throws JspException;

	@Override
	public void doTagCallback() throws JspException {
		if (varValueString != null) {
			String valueString;
			SingleFilterComponent<E, T> comp = getSingleFilterComponent();
			T value = comp.getValue();
			if (value == null) {
				return;
			}
			if (getConv() != null) {
				valueString = getConv().toString(value);
			} else {
				valueString = String.valueOf(value);
			}
			setPageScopeAttribute(varValueString, valueString);
		}
	}
	@Override
	protected void setValueProperty() throws JspException {
		ValueDto<T> valueDto = setValuePropertyHelper(isValueSet(), getValue(), JbfilterTagAttribute.value);
		if (valueDto.doSetValue) {
			SingleFilterComponent<E, T> comp = getSingleFilterComponent();
			comp.setValue(valueDto.value);
		}
	}
	@Override
	protected void setValueCleanValueProperty() throws JspException {
		ValueDto<T> valueDto = setValuePropertyHelper(isValueCleanValueSet(), getValueCleanValue(), JbfilterTagAttribute.valueCleanValue);
		if (valueDto.doSetValue) {
			SingleFilterComponent<E, T> comp = getSingleFilterComponent();
			comp.setValueCleanValue(valueDto.value);
		}
	}
	private ValueDto<T> setValuePropertyHelper(boolean valueSet, Object value, JbfilterTagAttribute tagAttribute) throws JspException {
		// do nothing if value was not set (the tag's value attribute was not used)
		if (!valueSet) {
			return new ValueDto<T>();
		}
		if (value == null) {
			return new ValueDto<T>(null);
		}
		boolean stringType = value instanceof String;
		// apply converter for string values
		if (getConv() != null && stringType) {
			try {
				return new ValueDto<T>(getConv().fromString((String)value));
			} catch (RuntimeException e) {
				// do nothing because this is wrong user input 
			}
		} else {
			try {
				@SuppressWarnings("unchecked")
				T valueCast = (T)value;
				return new ValueDto<T>(valueCast);
			} catch (ClassCastException e) {
				throw new JspException(
					new StringBuilder().
					append("Invalid type for attribute '").
					append(tagAttribute).
					append("' in tag '").
					append(getJbfilterTag()).
					append("'.").toString(), e);
			}
		}
		return new ValueDto<T>();
	}
	private static class ValueDto<T> {
		private boolean doSetValue;
		private T value;
		private ValueDto() {
		}
		private ValueDto(T value) {
			this.doSetValue = true;
			this.value = value;
		}
	}

	private Converter<T> getConv() throws JspException {
		if (conv == null) {
			conv = retrieveConverter();
		}
		return conv;
	}
	private Converter<T> retrieveConverter() throws JspException {
		if (converter == null || converter instanceof Converter) {
			@SuppressWarnings("unchecked")
			Converter<T> conv = (Converter<T>)converter;
			return conv;
		}
		if (converter instanceof String) {
			if (StringUtils.isNotBlank((String)converter)) {
				try {
					@SuppressWarnings("unchecked")
					Converter<T> conv = (Converter<T>)StandardConverter.findConverter((String)converter);
					return conv;
				} catch (ClassNotFoundException e) {
					throw new JspException(String.format("Could not find class '%s'", converter), e);
				}
			} else {
				return null;
			}
		}
		throw new JspException(JbfilterTagAttribute.converter + " must be a " + Converter.class + " or a " + String.class + ".");
	}

	public void setVarValueString(String varValueString) {
		this.varValueString = varValueString;
	}

}
